﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="InvokeMethodExpressionParserTests.cs" company="JDML Consulting Ltd.">
//   Copyright 2011 Photon Project - http://photon.codeplex.com
// </copyright>
// <summary>
//   Defines the InvokeMethodExpressionParserTests type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Photon.Windows
{
    using Input;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using System;
    using System.Collections.Generic;
    using Testing;

    [TestClass]
    public class InvokeMethodExpressionParserTests
    {
        #region Fields

        private InvokeMethodExpressionParser _parser;

        #endregion

        [TestInitialize]
        public void TestInitialize()
        {
            _parser = new InvokeMethodExpressionParser();
        }
        
        [TestMethod]
        public void When_Parse_With_No_Parameters()
        {
            var actual = _parser.Parse("method()");
            AssertAreEqual(new InvokeMethodExpression("method",
                new InvokeMethodTypeReference[0]), actual);
        }

        [TestMethod]
        public void When_Parse_With_No_Parameters_And_No_Braces()
        {
            var actual = _parser.Parse("method");
            AssertAreEqual(new InvokeMethodExpression("method",
                new InvokeMethodTypeReference[0]), actual);
        }

        [TestMethod]
        public void When_Parse_With_Typed_Parameter()
        {
            var actual = _parser.Parse("method(int)");
            AssertAreEqual(new InvokeMethodExpression("method",
                new[] {
                    new InvokeMethodTypeReference("int")
                }), actual);
        }

        [TestMethod]
        public void When_Parse_With_Multiple_Typed_Parameters()
        {
            var actual = _parser.Parse("method(int, double)");
            AssertAreEqual(new InvokeMethodExpression("method",
                new[] {
                    new InvokeMethodTypeReference("int"),
                    new InvokeMethodTypeReference("double")
                }), actual);
        }
        
        [TestMethod]
        public void When_Parse_With_Untyped_Parameter()
        {
            var actual = _parser.Parse("method(,int)");
            AssertAreEqual(new InvokeMethodExpression("method",
                new[] {
                    new InvokeMethodTypeReference(string.Empty),
                    new InvokeMethodTypeReference("int")
                }), actual);
        }

        [TestMethod]
        public void When_Parse_With_Multiple_Untyped_Parameters()
        {
            var actual = _parser.Parse("method(,,)");
            AssertAreEqual(new InvokeMethodExpression("method",
                new[] {
                    new InvokeMethodTypeReference(string.Empty),
                    new InvokeMethodTypeReference(string.Empty),
                    new InvokeMethodTypeReference(string.Empty)
                }), actual);
        }

        [TestMethod]
        public void When_Parse_With_Mixed_Typed_And_Untyped_Parameters()
        {
            var actual = _parser.Parse("method(,int,string,,,double)");
            AssertAreEqual(new InvokeMethodExpression("method",
                new[] {
                    new InvokeMethodTypeReference(string.Empty),
                    new InvokeMethodTypeReference("int"),
                    new InvokeMethodTypeReference("string"),
                    new InvokeMethodTypeReference(string.Empty),
                    new InvokeMethodTypeReference(string.Empty),
                    new InvokeMethodTypeReference("double")
                }), actual);
        }

        [TestMethod]
        public void When_Parse_With_Qualified_Typed_Parameters()
        {
            var actual = _parser.Parse("method(System.Int32, System.String, System.Collections.IList)");
            AssertAreEqual(new InvokeMethodExpression("method",
                new[] {
                    new InvokeMethodTypeReference("System.Int32"),
                    new InvokeMethodTypeReference("System.String"),
                    new InvokeMethodTypeReference("System.Collections.IList")
                }), actual);
        }
        
        [TestMethod]
        public void When_Parse_With_Nullable_Typed_Parameters()
        {
            var actual = _parser.Parse("method(int?, string, bool?)");
            AssertAreEqual(new InvokeMethodExpression("method",
                new[] {
                    new InvokeMethodTypeReference("int?"),
                    new InvokeMethodTypeReference("string"),
                    new InvokeMethodTypeReference("bool?")
                }), actual);
        }

        [TestMethod]
        public void When_Parse_With_Generic_Typed_Parameters()
        {
            var actual = _parser.Parse("method(IList<int>, IList{})");
            AssertAreEqual(new InvokeMethodExpression("method",
                new[] {
                    new InvokeMethodTypeReference("IList", 
                        new []
                        {
                            new InvokeMethodTypeReference("int"),
                        }),
                    new InvokeMethodTypeReference("IList", 
                        new []
                        {
                            new InvokeMethodTypeReference(string.Empty),
                        })
                }), actual);
        }

        [TestMethod]
        public void When_Parse_With_Nested_Generic_Typed_Parameters()
        {
            var actual = _parser.Parse("method(IList<IDictionary<int, string>>)");
            AssertAreEqual(new InvokeMethodExpression("method",
                new[] {
                    new InvokeMethodTypeReference("IList", 
                        new []
                        {
                            new InvokeMethodTypeReference("IDictionary", 
                                new []
                                {
                                    new InvokeMethodTypeReference("int"),
                                    new InvokeMethodTypeReference("string"),
                                }),
                        }),
                }), actual);
        }

        [TestMethod]
        public void When_Parse_With_WildCards()
        {
            var actual = _parser.Parse("method(*, IDictionary<*, *>)");
            AssertAreEqual(new InvokeMethodExpression("method",
                new[] {
                    new InvokeMethodTypeReference(string.Empty),
                    new InvokeMethodTypeReference("IDictionary", 
                        new []
                        {
                            new InvokeMethodTypeReference(string.Empty), 
                            new InvokeMethodTypeReference(string.Empty)
                        })
                }), actual);
        }
        
        [TestMethod]
        public void When_Parse_Invalid_Expression()
        {
            AssertException.Throws<FormatException>(() => _parser.Parse("method(()"));
            AssertException.Throws<FormatException>(() => _parser.Parse("method())"));
            AssertException.Throws<FormatException>(() => _parser.Parse("method(),"));
            AssertException.Throws<FormatException>(() => _parser.Parse("method(,"));
            AssertException.Throws<FormatException>(() => _parser.Parse("method(System.,"));
            AssertException.Throws<FormatException>(() => _parser.Parse("method(**)"));
            AssertException.Throws<FormatException>(() => _parser.Parse("method(IList{{int})"));
            AssertException.Throws<FormatException>(() => _parser.Parse("method(IList{int}})"));
            AssertException.Throws<FormatException>(() => _parser.Parse("method(IList?<int>)"));
            AssertException.Throws<FormatException>(() => _parser.Parse("method(int, string,,*?)"));
        }

        /// <summary>
        /// Asserts the supplied <see cref="InvokeMethodExpression"/> instances are equal.
        /// </summary>
        /// <param name="expected">The expected.</param>
        /// <param name="actual">The actual.</param>
        internal void AssertAreEqual(InvokeMethodExpression expected, InvokeMethodExpression actual)
        {
            Assert.AreEqual(expected.MethodName, actual.MethodName);
            AssertParameterTypesAreEqual(expected.ParameterTypes, actual.ParameterTypes);
        }

        /// <summary>
        /// Asserts the parameter types are equal.
        /// </summary>
        /// <param name="expected">The expected.</param>
        /// <param name="actual">The actual.</param>
        private static void AssertParameterTypesAreEqual(IList<InvokeMethodTypeReference> expected, IList<InvokeMethodTypeReference> actual)
        {
            Assert.AreEqual(expected.Count, actual.Count);
            for (var i=0; i<expected.Count; i++)
            {
                Assert.AreEqual(expected[i].TypeName, actual[i].TypeName, 
                    "Parameters do not have matching \"TypeName\" properties, index {0}.", i);
                AssertParameterTypesAreEqual(expected[i].GenericArgumentTypes, 
                    actual[i].GenericArgumentTypes);
            }
        }
    }
}
